using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using PentaBiz.Interop;

namespace PentaBiz.Serialization
{
    /// <summary>
    /// extremly fast binary serializer.
    /// Original from: https://github.com/tomba/netserializer
    /// Modified to handle T static serialization instead of global static serialization types (Modified Initialize behavior)
    /// </summary>
    /// <typeparam name="T">Type of the top level class to serialize</typeparam>
    public static partial class NetSerializer<T>
    {
        private static Dictionary<Type, ushort> s_typeIDMap;

        private delegate void SerializerSwitch(Stream stream, object ob);

        private delegate void DeserializerSwitch(Stream stream, out object ob);

        private static SerializerSwitch s_serializerSwitch;
        private static DeserializerSwitch s_deserializerSwitch;

        private static bool s_initialized;

        static NetSerializer()
        {
            Initialize(typeof(T));
        }
        public static void Initialize() { }
        private static void Initialize(params Type[] rootTypes)
        {
            if (s_initialized)
                throw new InvalidOperationException("NetSerializer already initialized");

            var types = CollectTypes(rootTypes);

            GenerateAssembly(types);

            s_typeIDMap = GenerateDynamic(types);

            s_initialized = true;
        }

        public static void Serialize(Stream stream, object data)
        {
            if (!s_initialized)
                throw new InvalidOperationException("NetSerializer not initialized");

            D("Serializing {0}", data.GetType().Name);

            s_serializerSwitch(stream, data);
        }

        public static object Deserialize(Stream stream)
        {
            if (!s_initialized)
                throw new InvalidOperationException("NetSerializer not initialized");

            D("Deserializing");

            object o;
            s_deserializerSwitch(stream, out o);
            return o;
        }

        [System.Diagnostics.Conditional("DEBUG")]
        private static void D(string fmt, params object[] args)
        {
            //Console.WriteLine("S: " + String.Format(fmt, args));
        }

        [System.Diagnostics.Conditional("DEBUG")]
        private static void D(ILGenerator ilGen, string fmt, params object[] args)
        {
            //ilGen.EmitWriteLine("E: " + String.Format(fmt, args));
        }

        private static void CollectTypes(Type type, HashSet<Type> typeSet)
        {
            if (typeSet.Contains(type))
                return;

            if (type.IsAbstract)
                return;

            if (type.IsInterface)
                return;

            if (!type.IsSerializable)
                throw new NotSupportedException(String.Format("Type {0} is not marked as Serializable",
                                                              type.FullName));

            if (type.ContainsGenericParameters)
                throw new NotSupportedException(String.Format("Type {0} contains generic parameters", type.FullName));

            typeSet.Add(type);

            if (type.IsArray)
            {
                CollectTypes(type.GetElementType(), typeSet);
            }
            else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
            {
                var args = type.GetGenericArguments();

                Debug.Assert(args.Length == 2);

                // Dictionary<K,V> is stored as KeyValuePair<K,V>[]

                var arrayType = typeof(KeyValuePair<,>).MakeGenericType(args).MakeArrayType();

                CollectTypes(arrayType, typeSet);
            }
            else
            {
                var fields = GetFieldInfos(type);

                foreach (var field in fields)
                    CollectTypes(field.FieldType, typeSet);
            }
        }

        private static Type[] CollectTypes(Type[] rootTypes)
        {
            var primitives = new Type[]
                                 {
                                     typeof (bool),
                                     typeof (byte), typeof (sbyte),
                                     typeof (char),
                                     typeof (ushort), typeof (short),
                                     typeof (uint), typeof (int),
                                     typeof (ulong), typeof (long),
                                     typeof (float), typeof (double),
                                     typeof (string),
                                 };


            var typeSet = new HashSet<Type>(primitives);

            foreach (var type in rootTypes)
                CollectTypes(type, typeSet);

            return typeSet
                .OrderBy(t => t.FullName, StringComparer.Ordinal)
                .ToArray();
        }

        private static Dictionary<Type, TypeData> GenerateTypeData(Type[] types)
        {
            var map = new Dictionary<Type, TypeData>(types.Length);

            // TypeID 0 is reserved for null
            ushort typeID = 1;
            foreach (var type in types)
            {
                var writer = Primitives.GetWritePrimitive(type);
                var reader = Primitives.GetReadPrimitive(type);

                if ((writer != null) != (reader != null))
                    throw new InvalidOperationException(String.Format("Missing a read or write primitive for {0}",
                                                                      type.FullName));

                var isStatic = writer != null;

                if (type.IsPrimitive && isStatic == false)
                    throw new InvalidOperationException(String.Format(
                        "Missing primitive read/write methods for {0}", type.FullName));

                var td = new TypeData(typeID++);

                if (isStatic)
                {
                    td.WriterMethodInfo = writer;
                    td.ReaderMethodInfo = reader;
                    td.IsDynamic = false;
                }
                else
                {
                    if (typeof(System.Runtime.Serialization.ISerializable).IsAssignableFrom(type))
                        throw new InvalidOperationException(
                            String.Format("Cannot serialize {0}: ISerializable not supported", type.FullName));

                    td.IsDynamic = true;
                }

                map[type] = td;
            }

            return map;
        }

        private static Dictionary<Type, ushort> GenerateDynamic(Type[] types)
        {
            Dictionary<Type, TypeData> map = GenerateTypeData(types);

            var nonStaticTypes = map.Where(kvp => kvp.Value.IsDynamic).Select(kvp => kvp.Key);

            /* generate stubs */
            foreach (var type in nonStaticTypes)
            {
                var dm = GenerateDynamicSerializerStub(type);
                map[type].WriterMethodInfo = dm;
                map[type].WriterILGen = dm.GetILGenerator();
            }

            foreach (var type in nonStaticTypes)
            {
                var dm = GenerateDynamicDeserializerStub(type);
                map[type].ReaderMethodInfo = dm;
                map[type].ReaderILGen = dm.GetILGenerator();
            }

            var serializerSwitchMethod = new DynamicMethod("SerializerSwitch", null,
                                                           new Type[] { typeof(Stream), typeof(object) },
                                                           typeof(NetSerializer<T>), true);
            serializerSwitchMethod.DefineParameter(1, ParameterAttributes.None, "stream");
            serializerSwitchMethod.DefineParameter(2, ParameterAttributes.None, "value");
            var serializerSwitchMethodInfo = serializerSwitchMethod;

            var deserializerSwitchMethod = new DynamicMethod("DeserializerSwitch", null,
                                                             new Type[] { typeof(Stream), typeof(object).MakeByRefType() },
                                                             typeof(NetSerializer<T>), true);
            deserializerSwitchMethod.DefineParameter(1, ParameterAttributes.None, "stream");
            deserializerSwitchMethod.DefineParameter(2, ParameterAttributes.Out, "value");
            var deserializerSwitchMethodInfo = deserializerSwitchMethod;

            var ctx = new CodeGenContext(map, serializerSwitchMethodInfo, deserializerSwitchMethodInfo);

            /* generate bodies */
            foreach (var type in nonStaticTypes)
                GenerateSerializerBody(ctx, type, map[type].WriterILGen);

            foreach (var type in nonStaticTypes)
                GenerateDeserializerBody(ctx, type, map[type].ReaderILGen);

            var ilGen = serializerSwitchMethod.GetILGenerator();
            GenerateSerializerSwitch(ctx, ilGen, map);
            s_serializerSwitch = (SerializerSwitch)serializerSwitchMethod.CreateDelegate(typeof(SerializerSwitch));

            ilGen = deserializerSwitchMethod.GetILGenerator();
            GenerateDeserializerSwitch(ctx, ilGen, map);
            s_deserializerSwitch =
                (DeserializerSwitch)deserializerSwitchMethod.CreateDelegate(typeof(DeserializerSwitch));

            return map.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.TypeID);
        }

        [Conditional("GENERATE_DEBUGGING_ASSEMBLY")]
        private static void GenerateAssembly(Type[] types)
        {
            Dictionary<Type, TypeData> map = GenerateTypeData(types);

            var nonStaticTypes = map.Where(kvp => kvp.Value.IsDynamic).Select(kvp => kvp.Key);

            var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("NetSerializerDebug"),
                                                                   AssemblyBuilderAccess.RunAndSave);
            var modb = ab.DefineDynamicModule("NetSerializerDebug.dll");
            var tb = modb.DefineType("NetSerializer", TypeAttributes.Public);

            /* generate stubs */
            foreach (var type in nonStaticTypes)
            {
                var mb = GenerateStaticSerializerStub(tb, type);
                map[type].WriterMethodInfo = mb;
                map[type].WriterILGen = mb.GetILGenerator();
            }

            foreach (var type in nonStaticTypes)
            {
                var dm = GenerateStaticDeserializerStub(tb, type);
                map[type].ReaderMethodInfo = dm;
                map[type].ReaderILGen = dm.GetILGenerator();
            }

            var serializerSwitchMethod = tb.DefineMethod("SerializerSwitch",
                                                         MethodAttributes.Public | MethodAttributes.Static, null,
                                                         new Type[] { typeof(Stream), typeof(object) });
            serializerSwitchMethod.DefineParameter(1, ParameterAttributes.None, "stream");
            serializerSwitchMethod.DefineParameter(2, ParameterAttributes.None, "value");
            var serializerSwitchMethodInfo = serializerSwitchMethod;

            var deserializerSwitchMethod = tb.DefineMethod("DeserializerSwitch",
                                                           MethodAttributes.Public | MethodAttributes.Static, null,
                                                           new Type[] { typeof(Stream), typeof(object).MakeByRefType() });
            deserializerSwitchMethod.DefineParameter(1, ParameterAttributes.None, "stream");
            deserializerSwitchMethod.DefineParameter(2, ParameterAttributes.Out, "value");
            var deserializerSwitchMethodInfo = deserializerSwitchMethod;

            var ctx = new CodeGenContext(map, serializerSwitchMethodInfo, deserializerSwitchMethodInfo);

            /* generate bodies */
            foreach (var type in nonStaticTypes)
                GenerateSerializerBody(ctx, type, map[type].WriterILGen);

            foreach (var type in nonStaticTypes)
                GenerateDeserializerBody(ctx, type, map[type].ReaderILGen);

            var ilGen = serializerSwitchMethod.GetILGenerator();
            GenerateSerializerSwitch(ctx, ilGen, map);

            ilGen = deserializerSwitchMethod.GetILGenerator();
            GenerateDeserializerSwitch(ctx, ilGen, map);

            tb.CreateType();
            ab.Save("NetSerializerDebug.dll");
        }

        /* called from the dynamically generated code */

        private static ushort GetTypeID(object ob)
        {
            ushort id;

            if (ob == null)
                return 0;

            if (s_typeIDMap.TryGetValue(ob.GetType(), out id) == false)
                throw new InvalidOperationException(String.Format("Unknown type {0}", ob.GetType().FullName));

            return id;
        }

        private static IEnumerable<FieldInfo> GetFieldInfos(Type type)
        {
            Debug.Assert(type.IsSerializable);

            var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance |
                                        BindingFlags.DeclaredOnly)
                .Where(fi => (fi.Attributes & FieldAttributes.NotSerialized) == 0)
                .OrderBy(f => f.Name, StringComparer.Ordinal);

            if (type.BaseType == null)
            {
                return fields;
            }
            else
            {
                var baseFields = GetFieldInfos(type.BaseType);
                return baseFields.Concat(fields);
            }
        }

        private sealed class CodeGenContext
        {
            private readonly Dictionary<Type, TypeData> m_typeMap;

            public CodeGenContext(Dictionary<Type, TypeData> typeMap, MethodInfo serializerSwitch,
                                  MethodInfo deserializerSwitch)
            {
                m_typeMap = typeMap;
                this.SerializerSwitchMethodInfo = serializerSwitch;
                this.DeserializerSwitchMethodInfo = deserializerSwitch;
            }

            public MethodInfo SerializerSwitchMethodInfo { get; private set; }
            public MethodInfo DeserializerSwitchMethodInfo { get; private set; }

            public MethodInfo GetWriterMethodInfo(Type type)
            {
                return m_typeMap[type].WriterMethodInfo;
            }

            public ILGenerator GetWriterILGen(Type type)
            {
                return m_typeMap[type].WriterILGen;
            }

            public MethodInfo GetReaderMethodInfo(Type type)
            {
                return m_typeMap[type].ReaderMethodInfo;
            }

            public ILGenerator GetReaderILGen(Type type)
            {
                return m_typeMap[type].ReaderILGen;
            }

            public bool IsDynamic(Type type)
            {
                return m_typeMap[type].IsDynamic;
            }
        }

        private sealed class TypeData
        {
            public TypeData(ushort typeID)
            {
                this.TypeID = typeID;
            }

            public readonly ushort TypeID;
            public bool IsDynamic;
            public MethodInfo WriterMethodInfo;
            public ILGenerator WriterILGen;
            public MethodInfo ReaderMethodInfo;
            public ILGenerator ReaderILGen;
        }

        public static class Primitives
        {
            internal static MethodInfo GetWritePrimitive(Type type)
            {
                if (type.IsEnum)
                    type = Enum.GetUnderlyingType(type);

                MethodInfo writer = typeof(Primitives).GetMethod("WritePrimitive",
                                                                 BindingFlags.Static | BindingFlags.Public |
                                                                 BindingFlags.ExactBinding, null,
                                                                 new Type[] { typeof(Stream), type }, null);

                if (writer != null)
                    return writer;

                if (type.IsGenericType)
                {
                    var genType = type.GetGenericTypeDefinition();

                    var mis = typeof(Primitives).GetMethods(BindingFlags.Static | BindingFlags.Public)
                        .Where(mi => mi.IsGenericMethod && mi.Name == "WritePrimitive");

                    foreach (var mi in mis)
                    {
                        var p = mi.GetParameters();

                        if (p.Length != 2)
                            continue;

                        if (p[0].ParameterType != typeof(Stream))
                            continue;

                        var paramType = p[1].ParameterType;

                        if (paramType.IsGenericType == false)
                            continue;

                        var genParamType = paramType.GetGenericTypeDefinition();

                        if (genType == genParamType)
                            return mi;
                    }
                }

                return null;
            }

            internal static MethodInfo GetReadPrimitive(Type type)
            {
                if (type.IsEnum)
                    type = Enum.GetUnderlyingType(type);

                var reader = typeof(Primitives).GetMethod("ReadPrimitive",
                                                          BindingFlags.Static | BindingFlags.Public |
                                                          BindingFlags.ExactBinding, null,
                                                          new Type[] { typeof(Stream), type.MakeByRefType() }, null);

                if (reader != null)
                    return reader;

                if (type.IsGenericType)
                {
                    var genType = type.GetGenericTypeDefinition();

                    var mis = typeof(Primitives).GetMethods(BindingFlags.Static | BindingFlags.Public)
                        .Where(mi => mi.IsGenericMethod && mi.Name == "ReadPrimitive");

                    foreach (var mi in mis)
                    {
                        var p = mi.GetParameters();

                        if (p.Length != 2)
                            continue;

                        if (p[0].ParameterType != typeof(Stream))
                            continue;

                        var paramType = p[1].ParameterType;

                        if (paramType.IsByRef == false)
                            continue;

                        paramType = paramType.GetElementType();

                        if (paramType.IsGenericType == false)
                            continue;

                        var genParamType = paramType.GetGenericTypeDefinition();

                        if (genType == genParamType)
                            return mi;
                    }
                }

                return null;
            }

            private static uint EncodeZigZag32(int n)
            {
                return (uint)((n << 1) ^ (n >> 31));
            }

            private static ulong EncodeZigZag64(long n)
            {
                return (ulong)((n << 1) ^ (n >> 63));
            }

            private static int DecodeZigZag32(uint n)
            {
                return (int)(n >> 1) ^ -(int)(n & 1);
            }

            private static long DecodeZigZag64(ulong n)
            {
                return (long)(n >> 1) ^ -(long)(n & 1);
            }

            private static uint ReadVarint32(Stream stream)
            {
                int result = 0;
                int offset = 0;

                for (; offset < 32; offset += 7)
                {
                    int b = stream.ReadByte();
                    if (b == -1)
                        throw new EndOfStreamException();

                    result |= (b & 0x7f) << offset;

                    if ((b & 0x80) == 0)
                        return (uint)result;
                }

                throw new InvalidDataException();
            }

            private static void WriteVarint32(Stream stream, uint value)
            {
                for (; value >= 0x80u; value >>= 7)
                    stream.WriteByte((byte)(value | 0x80u));

                stream.WriteByte((byte)value);
            }

            private static ulong ReadVarint64(Stream stream)
            {
                long result = 0;
                int offset = 0;

                for (; offset < 64; offset += 7)
                {
                    int b = stream.ReadByte();
                    if (b == -1)
                        throw new EndOfStreamException();

                    result |= ((long)(b & 0x7f)) << offset;

                    if ((b & 0x80) == 0)
                        return (ulong)result;
                }

                throw new InvalidDataException();
            }

            private static void WriteVarint64(Stream stream, ulong value)
            {
                for (; value >= 0x80u; value >>= 7)
                    stream.WriteByte((byte)(value | 0x80u));

                stream.WriteByte((byte)value);
            }


            public static void WritePrimitive(Stream stream, bool value)
            {
                stream.WriteByte(value ? (byte)1 : (byte)0);
            }

            public static void ReadPrimitive(Stream stream, out bool value)
            {
                var b = stream.ReadByte();
                value = b != 0;
            }

            public static void WritePrimitive(Stream stream, byte value)
            {
                stream.WriteByte(value);
            }

            public static void ReadPrimitive(Stream stream, out byte value)
            {
                value = (byte)stream.ReadByte();
            }

            public static void WritePrimitive(Stream stream, sbyte value)
            {
                stream.WriteByte((byte)value);
            }

            public static void ReadPrimitive(Stream stream, out sbyte value)
            {
                value = (sbyte)stream.ReadByte();
            }

            public static void WritePrimitive(Stream stream, char value)
            {
                WriteVarint32(stream, value);
            }

            public static void ReadPrimitive(Stream stream, out char value)
            {
                value = (char)ReadVarint32(stream);
            }

            public static void WritePrimitive(Stream stream, ushort value)
            {
                WriteVarint32(stream, value);
            }

            public static void ReadPrimitive(Stream stream, out ushort value)
            {
                value = (ushort)ReadVarint32(stream);
            }

            public static void WritePrimitive(Stream stream, short value)
            {
                WriteVarint32(stream, EncodeZigZag32(value));
            }

            public static void ReadPrimitive(Stream stream, out short value)
            {
                value = (short)DecodeZigZag32(ReadVarint32(stream));
            }

            public static void WritePrimitive(Stream stream, uint value)
            {
                WriteVarint32(stream, value);
            }

            public static void ReadPrimitive(Stream stream, out uint value)
            {
                value = ReadVarint32(stream);
            }

            public static void WritePrimitive(Stream stream, int value)
            {
                WriteVarint32(stream, EncodeZigZag32(value));
            }

            public static void ReadPrimitive(Stream stream, out int value)
            {
                value = DecodeZigZag32(ReadVarint32(stream));
            }

            public static void WritePrimitive(Stream stream, ulong value)
            {
                WriteVarint64(stream, value);
            }

            public static void ReadPrimitive(Stream stream, out ulong value)
            {
                value = ReadVarint64(stream);
            }

            public static void WritePrimitive(Stream stream, long value)
            {
                WriteVarint64(stream, EncodeZigZag64(value));
            }

            public static void ReadPrimitive(Stream stream, out long value)
            {
                value = DecodeZigZag64(ReadVarint64(stream));
            }

#if !NO_UNSAFE
            public static unsafe void WritePrimitive(Stream stream, float value)
            {
                uint v = *(uint*)(&value);
                WriteVarint32(stream, v);
            }

            public static unsafe void ReadPrimitive(Stream stream, out float value)
            {
                uint v = ReadVarint32(stream);
                value = *(float*)(&v);
            }

            public static unsafe void WritePrimitive(Stream stream, double value)
            {
                ulong v = *(ulong*)(&value);
                WriteVarint64(stream, v);
            }

            public static unsafe void ReadPrimitive(Stream stream, out double value)
            {
                ulong v = ReadVarint64(stream);
                value = *(double*)(&v);
            }
#else
public static void WritePrimitive(Stream stream, float value)
{
WritePrimitive(stream, (double)value);
}

public static void ReadPrimitive(Stream stream, out float value)
{
double v;
ReadPrimitive(stream, out v);
value = (float)v;
}

public static void WritePrimitive(Stream stream, double value)
{
ulong v = (ulong)BitConverter.DoubleToInt64Bits(value);
WriteVarint64(stream, v);
}

public static void ReadPrimitive(Stream stream, out double value)
{
ulong v = ReadVarint64(stream);
value = BitConverter.Int64BitsToDouble((long)v);
}
#endif

            #region DateTime
            public static void WritePrimitive(Stream stream, DateTime value)
            {
                long v = value.ToBinary();
                WritePrimitive(stream, v);
            }

            public static void ReadPrimitive(Stream stream, out DateTime value)
            {
                long v;
                ReadPrimitive(stream, out v);
                value = DateTime.FromBinary(v);
            }
            #endregion

            #region TimeSpan
            public static void WritePrimitive(Stream stream, TimeSpan value)
            {
                long v = value.Ticks;
                WritePrimitive(stream, v);
            }

            public static void ReadPrimitive(Stream stream, out TimeSpan value)
            {
                long v;
                ReadPrimitive(stream, out v);
                value = new TimeSpan(v);
            }
            #endregion

            public static void WritePrimitive(Stream stream, string value)
            {
                if (value == null)
                {
                    WritePrimitive(stream, (uint)0);
                    return;
                }

#if GC_NICE_VERSION
WritePrimitive(stream, (uint)value.Length + 1);

foreach (char c in value)
WritePrimitive(stream, c);
#else

                var encoding = new UTF8Encoding(false, true);

                int len = encoding.GetByteCount(value);

                WritePrimitive(stream, (uint)len + 1);

                var buf = new byte[len];

                encoding.GetBytes(value, 0, value.Length, buf, 0);

                stream.Write(buf, 0, len);
#endif
            }

            public static void ReadPrimitive(Stream stream, out string value)
            {
                uint len;
                ReadPrimitive(stream, out len);

                if (len == 0)
                {
                    value = null;
                    return;
                }
                else if (len == 1)
                {
                    value = string.Empty;
                    return;
                }

                len -= 1;

#if GC_NICE_VERSION
var arr = new char[len];
for (uint i = 0; i < len; ++i)
ReadPrimitive(stream, out arr[i]);

value = new string(arr);
#else

                var encoding = new UTF8Encoding(false, true);

                var buf = new byte[len];

                int l = 0;

                while (l < len)
                {
                    int r = stream.Read(buf, l, (int)len - l);
                    if (r == 0)
                        throw new EndOfStreamException();
                    l += r;
                }

                value = encoding.GetString(buf);
#endif
            }

            #region doubles
            public static void WritePrimitive(Stream stream, double[] value)
            {
                if (value == null)
                {
                    WritePrimitive(stream, (uint)0);
                    return;
                }

                //write out value.Length
                WritePrimitive(stream, (uint)value.Length + 1);

                //write out Marshalled bytes
                var b = value.MarshalToByteArray();
                WritePrimitive(stream, b);
            }
            static double[] s_emptyDoubleArray = new double[0];
            public static void ReadPrimitive(Stream stream, out double[] value)
            {
                //read value.Length
                uint len;
                ReadPrimitive(stream, out len);

                if (len == 0)
                {
                    value = null;
                    return;
                }
                if (len == 1)
                {
                    value = s_emptyDoubleArray;
                    return;
                }

                len -= 1;

                byte[] b;
                ReadPrimitive(stream, out b);

                value = b.ReadOneDimensionDoubleArray(len);
            }

            public static void WritePrimitive(Stream stream, double[,] value)
            {
                if (value == null)
                {
                    WritePrimitive(stream, (uint)0);
                    return;
                }

                WritePrimitive(stream, (uint)1);


                //write out value.Length
                WritePrimitive(stream, (uint)value.GetLength(0));


                //write out value.Length2
                WritePrimitive(stream, (uint)value.GetLength(1));

                //write out Marshalled bytes
                var b = value.MarshalToByteArray();
                WritePrimitive(stream, b);


            }
            public static void ReadPrimitive(Stream stream, out double[,] value)
            {
                //read isnull
                uint notNull;
                ReadPrimitive(stream, out notNull);

                if (notNull == 0)
                {
                    value = null;
                    return;
                }

                //read value.Length1
                uint len1;
                ReadPrimitive(stream, out len1);

                //read value.Length2
                uint len2;
                ReadPrimitive(stream, out len2);

                byte[] b;
                ReadPrimitive(stream, out b);

                value = b.ReadTwoDimensionDoubleArray(len1, len2);
            }

            public static void WritePrimitive(Stream stream, double[, ,] value)
            {
                if (value == null)
                {
                    WritePrimitive(stream, (uint)0);
                    return;
                }

                WritePrimitive(stream, (uint)1);


                //write out value.Length1
                WritePrimitive(stream, (uint)value.GetLength(0));

                //write out value.Length2
                WritePrimitive(stream, (uint)value.GetLength(1));

                //write out value.Length3
                WritePrimitive(stream, (uint)value.GetLength(2));

                //write out Marshalled bytes
                var b = value.MarshalToByteArray();
                WritePrimitive(stream, b);


            }
            public static void ReadPrimitive(Stream stream, out double[, ,] value)
            {
                //read isnull
                uint notNull;
                ReadPrimitive(stream, out notNull);

                if (notNull == 0)
                {
                    value = null;
                    return;
                }

                //read value.Length1
                uint len1;
                ReadPrimitive(stream, out len1);

                //read value.Length2
                uint len2;
                ReadPrimitive(stream, out len2);

                //read value.Length3
                uint len3;
                ReadPrimitive(stream, out len3);

                byte[] b;
                ReadPrimitive(stream, out b);

                value = b.ReadThreeDimensionDoubleArray(len1, len2, len3);
            }
            #endregion

            #region decimals
            public static void WritePrimitive(Stream stream, decimal[] value)
            {
                if (value == null)
                {
                    WritePrimitive(stream, (uint)0);
                    return;
                }

                //write out value.Length
                WritePrimitive(stream, (uint)value.Length + 1);

                //write out Marshalled bytes
                var b = value.MarshalToByteArray();
                WritePrimitive(stream, b);
            }
            static decimal[] s_emptydecimalArray = new decimal[0];
            public static void ReadPrimitive(Stream stream, out decimal[] value)
            {
                //read value.Length
                uint len;
                ReadPrimitive(stream, out len);

                if (len == 0)
                {
                    value = null;
                    return;
                }
                if (len == 1)
                {
                    value = s_emptydecimalArray;
                    return;
                }

                len -= 1;

                byte[] b;
                ReadPrimitive(stream, out b);

                value = b.ReadOneDimensiondecimalArray(len);
            }

            public static void WritePrimitive(Stream stream, decimal[,] value)
            {
                if (value == null)
                {
                    WritePrimitive(stream, (uint)0);
                    return;
                }

                WritePrimitive(stream, (uint)1);


                //write out value.Length
                WritePrimitive(stream, (uint)value.GetLength(0));


                //write out value.Length2
                WritePrimitive(stream, (uint)value.GetLength(1));

                //write out Marshalled bytes
                var b = value.MarshalToByteArray();
                WritePrimitive(stream, b);


            }
            public static void ReadPrimitive(Stream stream, out decimal[,] value)
            {
                //read isnull
                uint notNull;
                ReadPrimitive(stream, out notNull);

                if (notNull == 0)
                {
                    value = null;
                    return;
                }

                //read value.Length1
                uint len1;
                ReadPrimitive(stream, out len1);

                //read value.Length2
                uint len2;
                ReadPrimitive(stream, out len2);

                byte[] b;
                ReadPrimitive(stream, out b);

                value = b.ReadTwoDimensiondecimalArray(len1, len2);
            }

            public static void WritePrimitive(Stream stream, decimal[, ,] value)
            {
                if (value == null)
                {
                    WritePrimitive(stream, (uint)0);
                    return;
                }

                WritePrimitive(stream, (uint)1);


                //write out value.Length1
                WritePrimitive(stream, (uint)value.GetLength(0));

                //write out value.Length2
                WritePrimitive(stream, (uint)value.GetLength(1));

                //write out value.Length3
                WritePrimitive(stream, (uint)value.GetLength(2));

                //write out Marshalled bytes
                var b = value.MarshalToByteArray();
                WritePrimitive(stream, b);


            }
            public static void ReadPrimitive(Stream stream, out decimal[, ,] value)
            {
                //read isnull
                uint notNull;
                ReadPrimitive(stream, out notNull);

                if (notNull == 0)
                {
                    value = null;
                    return;
                }

                //read value.Length1
                uint len1;
                ReadPrimitive(stream, out len1);

                //read value.Length2
                uint len2;
                ReadPrimitive(stream, out len2);

                //read value.Length3
                uint len3;
                ReadPrimitive(stream, out len3);

                byte[] b;
                ReadPrimitive(stream, out b);

                value = b.ReadThreeDimensiondecimalArray(len1, len2, len3);
            }
            #endregion

            #region floats
            public static void WritePrimitive(Stream stream, float[] value)
            {
                if (value == null)
                {
                    WritePrimitive(stream, (uint)0);
                    return;
                }

                //write out value.Length
                WritePrimitive(stream, (uint)value.Length + 1);

                //write out Marshalled bytes
                var b = value.MarshalToByteArray();
                WritePrimitive(stream, b);
            }
            static float[] s_emptyfloatArray = new float[0];
            public static void ReadPrimitive(Stream stream, out float[] value)
            {
                //read value.Length
                uint len;
                ReadPrimitive(stream, out len);

                if (len == 0)
                {
                    value = null;
                    return;
                }
                if (len == 1)
                {
                    value = s_emptyfloatArray;
                    return;
                }

                len -= 1;

                byte[] b;
                ReadPrimitive(stream, out b);

                value = b.ReadOneDimensionfloatArray(len);
            }

            public static void WritePrimitive(Stream stream, float[,] value)
            {
                if (value == null)
                {
                    WritePrimitive(stream, (uint)0);
                    return;
                }

                WritePrimitive(stream, (uint)1);


                //write out value.Length
                WritePrimitive(stream, (uint)value.GetLength(0));


                //write out value.Length2
                WritePrimitive(stream, (uint)value.GetLength(1));

                //write out Marshalled bytes
                var b = value.MarshalToByteArray();
                WritePrimitive(stream, b);


            }
            public static void ReadPrimitive(Stream stream, out float[,] value)
            {
                //read isnull
                uint notNull;
                ReadPrimitive(stream, out notNull);

                if (notNull == 0)
                {
                    value = null;
                    return;
                }

                //read value.Length1
                uint len1;
                ReadPrimitive(stream, out len1);

                //read value.Length2
                uint len2;
                ReadPrimitive(stream, out len2);

                byte[] b;
                ReadPrimitive(stream, out b);

                value = b.ReadTwoDimensionfloatArray(len1, len2);
            }

            public static void WritePrimitive(Stream stream, float[, ,] value)
            {
                if (value == null)
                {
                    WritePrimitive(stream, (uint)0);
                    return;
                }

                WritePrimitive(stream, (uint)1);


                //write out value.Length1
                WritePrimitive(stream, (uint)value.GetLength(0));

                //write out value.Length2
                WritePrimitive(stream, (uint)value.GetLength(1));

                //write out value.Length3
                WritePrimitive(stream, (uint)value.GetLength(2));

                //write out Marshalled bytes
                var b = value.MarshalToByteArray();
                WritePrimitive(stream, b);


            }
            public static void ReadPrimitive(Stream stream, out float[, ,] value)
            {
                //read isnull
                uint notNull;
                ReadPrimitive(stream, out notNull);

                if (notNull == 0)
                {
                    value = null;
                    return;
                }

                //read value.Length1
                uint len1;
                ReadPrimitive(stream, out len1);

                //read value.Length2
                uint len2;
                ReadPrimitive(stream, out len2);

                //read value.Length3
                uint len3;
                ReadPrimitive(stream, out len3);

                byte[] b;
                ReadPrimitive(stream, out b);

                value = b.ReadThreeDimensionfloatArray(len1, len2, len3);
            }
            #endregion

            public static void WritePrimitive(Stream stream, byte[] value)
            {
                if (value == null)
                {
                    WritePrimitive(stream, (uint)0);
                    return;
                }

                WritePrimitive(stream, (uint)value.Length + 1);

                stream.Write(value, 0, value.Length);
            }

            private static readonly byte[] s_emptyByteArray = new byte[0];

            public static void ReadPrimitive(Stream stream, out byte[] value)
            {
                uint len;
                ReadPrimitive(stream, out len);

                if (len == 0)
                {
                    value = null;
                    return;
                }
                else if (len == 1)
                {
                    value = s_emptyByteArray;
                    return;
                }

                len -= 1;

                value = new byte[len];
                int l = 0;

                while (l < len)
                {
                    int r = stream.Read(value, l, (int)len - l);
                    if (r == 0)
                        throw new EndOfStreamException();
                    l += r;
                }
            }

            public static void WritePrimitive<TKey, TValue>(Stream stream, Dictionary<TKey, TValue> value)
            {
                var kvpArray = new KeyValuePair<TKey, TValue>[value.Count];

                int i = 0;
                foreach (var kvp in value)
                    kvpArray[i++] = kvp;

                NetSerializer<T>.Serialize(stream, kvpArray);
            }

            public static void ReadPrimitive<TKey, TValue>(Stream stream, out Dictionary<TKey, TValue> value)
            {
                var kvpArray = (KeyValuePair<TKey, TValue>[])NetSerializer<T>.Deserialize(stream);

                value = new Dictionary<TKey, TValue>(kvpArray.Length);

                foreach (var kvp in kvpArray)
                    value.Add(kvp.Key, kvp.Value);
            }
        }
    }
}